/* This file is part of swapper project
 *
 * Copyright (C) 2020 The Swapper Project Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.swapper.json;

import com.swapper.json.spi.JsonSupportsManager;
import com.swapper.reflect.TypeHelper;
import com.swapper.reflect.creator.InstanceCreator;
import com.swapper.reflect.creator.InstanceCreatorFactory;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * The JSON serialization and deserialization context.
 * It is used to configure parameters of serialization and deserialization,
 * and cache reusable information to improve efficiency.
 */
public final class JsonContext {
  private DateFormat _globalDateFormat;

  private final Collection<? extends JsonWrapperFactory> _factories;
  private final Map<Type, Class<?>> _rawTypeCache = new ConcurrentHashMap<>();
  private final Map<Type, InstanceCreator<?>> _creatorCache = new ConcurrentHashMap<>();
  private final Map<Type, JsonWrapper<?>> _wrapperCache = new ConcurrentHashMap<>();
  private final Map<Field, JsonWrapper<?>> _fieldWrapperCache = new ConcurrentHashMap<>();

  private static final JsonContext DEFAULT = new JsonContext();

  public static JsonContext getDefault() {
    return DEFAULT;
  }

  /**
   * The default constructor.
   */
  public JsonContext() {
    this(Collections.emptyList());
  }

  /**
   * The constructor.
   *
   * @param factories the custom JSON wrapper factories.
   * @throws NullPointerException if the specified factory collection is null.
   */
  public JsonContext(Collection<? extends JsonWrapperFactory> factories) {
    _factories = JsonSupportsManager.supports(factories);
  }

  public DateFormat getGlobalDateFormat() {
    return _globalDateFormat;
  }

  public void setGlobalDateFormat(DateFormat format) {
    _globalDateFormat = format;
  }

  /**
   * Get the raw type.
   * If it is not in the cache, create a new one.
   *
   * @param type the specified type.
   * @return the raw type.
   */
  public Class<?> getRawType(Type type) {
    Class<?> rawType;
    if ((rawType = _rawTypeCache.get(type)) == null) {
      rawType = TypeHelper.getRawType(type);
      _rawTypeCache.put(type, rawType);
    }
    return rawType;
  }

  /**
   * Get the instance creator.
   * If it is not in the cache, create a new one.
   *
   * @param type the specified type.
   * @param <T>  the specified generic type.
   * @return an instance creator instance.
   */
  @SuppressWarnings("unchecked")
  public <T> InstanceCreator<T> getInstanceCreator(Type type) {
    InstanceCreator<?> creator;
    if ((creator = _creatorCache.get(type)) == null) {
      creator = InstanceCreatorFactory.create(type, getRawType(type));
      _creatorCache.put(type, creator);
    }
    return (InstanceCreator<T>) creator;
  }

  /**
   * Get the JSON wrapper from type.
   * If it is not in the cache, create a new one.
   *
   * @param type the specified type.
   * @param <T>  the specified generic type.
   * @return a JSON wrapper instance.
   */
  @SuppressWarnings("unchecked")
  public <T> JsonWrapper<T> findWrapper(Type type) {
    JsonWrapper<?> wrapper;
    if ((wrapper = _wrapperCache.get(type)) == null) {
      for (JsonWrapperFactory factory : _factories) {
        if ((wrapper = factory.create(this, type)) != null) {
          _wrapperCache.put(type, wrapper);
          break;
        }
      }
      if (wrapper == null) {
        throw new IllegalStateException(
          String.format("Unsupported type in current version: %s", TypeHelper.name(type))
        );
      }
    }
    return (JsonWrapper<T>) wrapper;
  }

  /**
   * Get the JSON wrapper from field.
   * If it is not in the cache, create a new one.
   *
   * @param field the specified field.
   * @param <T>   the specified generic type.
   * @return a JSON wrapper instance.
   */
  @SuppressWarnings("unchecked")
  public <T> JsonWrapper<T> findWrapper(Field field) {
    JsonWrapper<?> wrapper;
    if ((wrapper = _fieldWrapperCache.get(field)) == null) {
      for (JsonWrapperFactory factory : _factories) {
        if ((wrapper = factory.create(this, field)) != null) {
          _fieldWrapperCache.put(field, wrapper);
          break;
        }
      }
      if (wrapper == null) {
        throw new IllegalStateException(
          String.format("Unsupported type in current version: %s", TypeHelper.name(field.getGenericType()))
        );
      }
    }
    return (JsonWrapper<T>) wrapper;
  }
}
